M2.992 · Anàlisi de xarxes complexes
20251 - Màster universitari en Ciència de dades (Data science)
Estudis d'Informàtica, Multimèdia i Telecomunicacions
- https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4161295/
- https://www.quanthub.com/designing-charts-axes-and-value-labels/
Disenyar i representar de forma correcta informació gràfica és una competència transversal que s'ha d'adquirir.
Alguns punts fonamentals:
- LLegendes clares i informatives incloent-hi les seves unitats.
- El format de les línies, punts i representacions gràfiques ha de ser tan informatiu i clar com sigui possible.
- Els textos superposats sobre altres textos o sobre informació gràfica es puntuarà, en general, de manera molt negativa.
PEC 3: Xarxes Complexes¶
En aquesta pràctica revisarem els conceptes apresos en els mòduls 1, 2 i 3 i seguirem introduint més nivells de complexitat realistes en l'anàlisi de xarxes complexes, tal i com recullen els materials del mòdul 4.
Les competències que es treballaran en aquesta activitat són les següents:
- Aprofundir en la comprensió de les propietats de les xarxes complexes que s’observen en dades empíriques.
- Aprofundir en els models de fluxos d’informació en el context de la propagació epidèmica.
- Conèixer i implementar models de contagi complex en el context de xarxes amb interaccions d’alt ordre.
- Conèixer i implementar algoritmes per analitzar xarxes temporals.
- Aprofundir en la comprensió de les dinàmiques de xarxa a diferents escales, tant per als estats dels nodes com per a l’evolució dels enllaços.
Consideracions generals:
- Aquesta prova d’avaluació s’ha de resoldre utilitzant la biblioteca
NetworkXi les altres biblioteques importades en l’enunciat. L’ús de qualsevol altra biblioteca s’ha de justificar dins de la mateixa activitat. - Aquesta PEC s’ha de realitzar de manera estrictament individual. Qualsevol indici de còpia serà penalitzat amb un suspens (D) per a totes les parts implicades i pot comportar l’avaluació negativa de l’assignatura de manera íntegra.
- És necessari que l’estudiant indiqui totes les fonts que ha utilitzat per a la realització de la PEC. En cas contrari, es considerarà que l’estudiant ha comès plagi, i serà penalitzat amb un suspens (D) i la possible avaluació negativa de l’assignatura de manera íntegra.
Format del lliurament:
- Alguns exercicis poden requerir diversos minuts d’execució, per la qual cosa el lliurament s’ha de fer en format notebook i en format HTML, on s’hi vegi el codi, els resultats i els comentaris de cada exercici. Es pot exportar el notebook a HTML des del menú File → Download as → HTML.
- Hi ha un tipus de cel·la especial per incloure text. Aquest tipus de cel·la us serà molt útil per respondre les diferents preguntes teòriques plantejades al llarg de l’activitat. Per canviar el tipus de cel·la, aneu al menú: Cell$\to$ Cell Type $\to$ Markdown.
Càrrega de llibreries¶
En la següent cel·la s’han de carregar totes les llibreries necessàries per a l’execució de l’activitat. Cal justificar l’ús de qualsevol llibreria addicional que no formi part del conjunt bàsic requerit.
# Llibreries bàsiques
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np
# Llibreries addicionals (justificar el seu ús)
Les versions de les llibreries que es recomanen per a aquesta activitat són les següents. No és necessari utilitzar aquestes llibreries, però són les que s’han utilitzat per provar la solució.
- NetworkX ver. 3.2.1
- Pandas ver. 2.2.3
- Numpy ver. 1.26.4
# Comprovar les versions
print("NetworkX ver. {}".format(nx.__version__))
print("Pandas ver. {}".format(pd.__version__))
print("Numpy ver. {}".format(np.__version__))
NetworkX ver. 3.2.1 Pandas ver. 2.2.3 Numpy ver. 1.26.4
Xarxa Multilacapa de "Game of Thrones" (6,5 punts)¶
Aquest primer exercici se centra en l’estudi de les interaccions dels personatges d’una de les sèries de televisió més populars dels darrers anys. Ens referim a "Game of Thrones", l’adaptació televisiva de la saga de novel·les riu "Cançó de Gel i Foc" de George R.R. Martin.
En el següent enllaç trobaràs les dades que utilitzarem per treballar en aquest exercici, les quals determinen les interaccions dels personatges de la sèrie per a les diferents temporades, així com l’explicació del que representen aquestes interaccions: https://github.com/mathbeveridge/gameofthrones/tree/master
Comencem.
Importa les dades generant els grafs per a cada capa, i obtén el nombre de nodes i arestes per a cadascuna d’elles. (0,25 punts)
Nota: En cas que hi hagi un node en l’arxiu d’arestes que no aparegui en el d’arxius de nodes, s’ha d’afegir.
import csv # Per crear un csv_reader i usar la funció next() per saltar les capçaleres # Bibliografia: [Ref.1]
def create_graph_of_season(nodes, edges):
graph = nx.Graph()
# -- Afegir nodes --
with open(nodes) as f:
csv_reader = csv.reader(f) # Bibliografia: [Ref.1]
header = next(csv_reader) # Bibliografia: [Ref.1]
for line in csv_reader:
id_character = line[0].strip()
label_character = line[1].strip()
graph.add_node(id_character, label=label_character) # Bibliografia: [Ref.2]
f.close()
current_nodes = list(graph.nodes()) # Per a comprovar si falta algun node (no esta aquí però sí a les arestes) # Bibliografia: [Ref.3]
# -- Afegir arestes --
with open(edges) as f:
csv_reader = csv.reader(f) # Bibliografia: [Ref.1]
header = next(csv_reader) # Bibliografia: [Ref.1]
for line in csv_reader:
source = line[0].strip()
target = line[1].strip()
weight = int(line[2].strip())
season = line[3].strip()
if source not in current_nodes:
graph.add_node(source, label=source.capitalize()) # Bibliografia: [Ref.4]
current_nodes = list(graph.nodes())
if target not in current_nodes:
graph.add_node(target, label=target.capitalize()) # Bibliografia: [Ref.4]
current_nodes = list(graph.nodes())
graph.add_edge(source, target, weight=weight, season=season)
f.close()
return graph
# -- Season 1 --
nodes_s1 = "gameofthrones/data/got-s1-nodes.csv"
edges_s1 = "gameofthrones/data/got-s1-edges.csv"
graph_s1 = create_graph_of_season(nodes_s1, edges_s1)
num_nodes_s1 = len(list(graph_s1.nodes()))
num_edges_s1 = len(list(graph_s1.edges()))
# -- Season 2 --
nodes_s2 = "gameofthrones/data/got-s2-nodes.csv"
edges_s2 = "gameofthrones/data/got-s2-edges.csv"
graph_s2 = create_graph_of_season(nodes_s2, edges_s2)
num_nodes_s2 = len(list(graph_s2.nodes()))
num_edges_s2 = len(list(graph_s2.edges()))
# -- Season 3 --
nodes_s3 = "gameofthrones/data/got-s3-nodes.csv"
edges_s3 = "gameofthrones/data/got-s3-edges.csv"
graph_s3 = create_graph_of_season(nodes_s3, edges_s3)
num_nodes_s3 = len(list(graph_s3.nodes()))
num_edges_s3 = len(list(graph_s3.edges()))
# -- Season 4 --
nodes_s4 = "gameofthrones/data/got-s4-nodes.csv"
edges_s4 = "gameofthrones/data/got-s4-edges.csv"
graph_s4 = create_graph_of_season(nodes_s4, edges_s4)
num_nodes_s4 = len(list(graph_s4.nodes()))
num_edges_s4 = len(list(graph_s4.edges()))
# -- Season 5 --
nodes_s5 = "gameofthrones/data/got-s5-nodes.csv"
edges_s5 = "gameofthrones/data/got-s5-edges.csv"
graph_s5 = create_graph_of_season(nodes_s5, edges_s5)
num_nodes_s5 = len(list(graph_s5.nodes()))
num_edges_s5 = len(list(graph_s5.edges()))
# -- Season 6 --
nodes_s6 = "gameofthrones/data/got-s6-nodes.csv"
edges_s6 = "gameofthrones/data/got-s6-edges.csv"
graph_s6 = create_graph_of_season(nodes_s6, edges_s6)
num_nodes_s6 = len(list(graph_s6.nodes()))
num_edges_s6 = len(list(graph_s6.edges()))
# -- Season 7 --
nodes_s7 = "gameofthrones/data/got-s7-nodes.csv"
edges_s7 = "gameofthrones/data/got-s7-edges.csv"
graph_s7 = create_graph_of_season(nodes_s7, edges_s7)
num_nodes_s7 = len(list(graph_s7.nodes()))
num_edges_s7 = len(list(graph_s7.edges()))
# -- Season 8 --
nodes_s8 = "gameofthrones/data/got-s8-nodes.csv"
edges_s8 = "gameofthrones/data/got-s8-edges.csv"
graph_s8 = create_graph_of_season(nodes_s8, edges_s8)
num_nodes_s8 = len(list(graph_s8.nodes()))
num_edges_s8 = len(list(graph_s8.edges()))
print("---------------------------------------- Temporada 1 ----------------------------------------")
print(f"Nodes de la temporada 1: {num_nodes_s1}")
print(f"Arestes de la temporada 1: {num_edges_s1}\n")
print("---------------------------------------- Temporada 2 ----------------------------------------")
print(f"Nodes de la temporada 2: {num_nodes_s2}")
print(f"Arestes de la temporada 2: {num_edges_s2}\n")
print("---------------------------------------- Temporada 3 ----------------------------------------")
print(f"Nodes de la temporada 3: {num_nodes_s3}")
print(f"Arestes de la temporada 3: {num_edges_s3}\n")
print("---------------------------------------- Temporada 4 ----------------------------------------")
print(f"Nodes de la temporada 4: {num_nodes_s4}")
print(f"Arestes de la temporada 4: {num_edges_s4}\n")
print("---------------------------------------- Temporada 5 ----------------------------------------")
print(f"Nodes de la temporada 5: {num_nodes_s5}")
print(f"Arestes de la temporada 5: {num_edges_s5}\n")
print("---------------------------------------- Temporada 6 ----------------------------------------")
print(f"Nodes de la temporada 6: {num_nodes_s6}")
print(f"Arestes de la temporada 6: {num_edges_s6}")
print("---------------------------------------- Temporada 7 ----------------------------------------")
print(f"Nodes de la temporada 7: {num_nodes_s7}")
print(f"Arestes de la temporada 7: {num_edges_s7}\n")
print("---------------------------------------- Temporada 8 ----------------------------------------")
print(f"Nodes de la temporada 8: {num_nodes_s8}")
print(f"Arestes de la temporada 8: {num_edges_s8}")
---------------------------------------- Temporada 1 ---------------------------------------- Nodes de la temporada 1: 126 Arestes de la temporada 1: 549 ---------------------------------------- Temporada 2 ---------------------------------------- Nodes de la temporada 2: 129 Arestes de la temporada 2: 486 ---------------------------------------- Temporada 3 ---------------------------------------- Nodes de la temporada 3: 124 Arestes de la temporada 3: 504 ---------------------------------------- Temporada 4 ---------------------------------------- Nodes de la temporada 4: 172 Arestes de la temporada 4: 667 ---------------------------------------- Temporada 5 ---------------------------------------- Nodes de la temporada 5: 119 Arestes de la temporada 5: 396 ---------------------------------------- Temporada 6 ---------------------------------------- Nodes de la temporada 6: 142 Arestes de la temporada 6: 541 ---------------------------------------- Temporada 7 ---------------------------------------- Nodes de la temporada 7: 81 Arestes de la temporada 7: 412 ---------------------------------------- Temporada 8 ---------------------------------------- Nodes de la temporada 8: 74 Arestes de la temporada 8: 553
Referències externes:
[Ref.1] -- How to Skip CSV Headers in Python: Process Data From Row 2 Without Editing Headers [en línia] [consulta: 30 de novembre de 2025]. Disponible a: https://www.pythontutorials.net/blog/how-to-skip-the-headers-when-processing-a-csv-file-using-python/#method-1-using-pythons-built-in-csv-module --> Web que explica com ignorar les capçaleres del fitxer csv.
[Ref.2] -- Graph.add_node [en línia] [consulta: 30 de novembre de 2025]. Disponible a: https://networkx.org/documentation/stable/reference/classes/generated/networkx.Graph.add_node.html --> Documentació ofical de la funció per a afegir nodes a un graf.
[Ref.3] -- Graph.nodes [en línia] [consulta: 30 de novembre de 2025]. Disponible a: https://networkx.org/documentation/stable/reference/classes/generated/networkx.Graph.nodes.html --> Documentació oficial de la funció per a obtenir els nodes d'un graf.
[Ref.4] -- Python String capitalize() Method [en línia] [consulta: 30 de novembre de 2025]. Disponible a: https://www.w3schools.com/python/ref_string_capitalize.asp --> Web que explica com posar la primera lletra d'un string en majúscula i la resta en minúscula.
La Figura 9 de l’article “A survey of community detection methods in multilayer networks” de Xinyu Huang i al. (2021), mostra una visualització de la xarxa multilayer de Joc de Trons, que conté les cinc primeres temporades. Basant-te en aquesta visualització, et demanem que en realitzis una de nova millorada que inclogui les vuit temporades.
Nota: Pots obtenir l’article en el següent enllaç: https://link.springer.com/article/10.1007/s10618-020-00716-6
# --- Aquest exercici ha estat resolt amb l'ajuda de [Ref.5] -> Ús 1 ---
got_dict = {1: graph_s1, 2: graph_s2, 3: graph_s3, 4: graph_s4, 5: graph_s5, 6: graph_s6, 7: graph_s7, 8: graph_s8} # {temporada: graf}
multilayer_got = nx.Graph()
for graph in got_dict.values(): # El compose() uneix tots els grafs de les temporades en un de sol -> Representa la info de tota la sèrie
multilayer_got = nx.compose(multilayer_got, graph) # Bibliografia: [Ref.6]
base_pos = nx.spring_layout(multilayer_got, k=3) # Bibliografia: [Ref.7]
layer_offset = {season: season * 3.0 for season in got_dict.keys()} # Per a que no es superposin els grafs de les temporades
# -- Càlcul posicions de cada capa --
layer_positions = {}
for character, (coord_x, coord_y) in base_pos.items():
for season in got_dict:
layer_positions[(character, season)] = (coord_x + layer_offset[season], coord_y)
# -- Construcció del graf multilayer per visualitzar --
got_multilayer_plot = nx.Graph()
for season, graph in got_dict.items():
for character in graph.nodes():
got_multilayer_plot.add_node((character, season))
# -- Connexions --
for season, graph in got_dict.items():
for character_1, character_2, args in graph.edges(data=True):
weight = args["weight"]
got_multilayer_plot.add_edge((character_1, season), (character_2, season), weight=weight)
# -- Connexions entre capes --
for character in multilayer_got.nodes():
for season in range(1, 8):
if character in got_dict[season] and character in got_dict[season + 1]:
got_multilayer_plot.add_edge((character, season), (character, season + 1))
# -- Visualització --
plt.figure(figsize=(100,100))
# - Dibuix connexions de cada capa -
for season in got_dict.keys():
edges = [edge for edge in got_multilayer_plot.edges() if edge[0][1] == season and edge[1][1] == season]
nx.draw_networkx_edges(got_multilayer_plot, layer_positions, edgelist=edges, edge_color='r') # Bibliografia: [Ref.8]
# - Dibuix connexions entre capes -
interlayer_edges = [edge for edge in got_multilayer_plot.edges() if edge[0][1] != edge[1][1]]
nx.draw_networkx_edges(got_multilayer_plot, layer_positions, edgelist=interlayer_edges, edge_color='b') # Bibliografia: [Ref.8]
# - Dibuix nodes -
nx.draw_networkx_nodes(got_multilayer_plot, layer_positions, node_size = 200, node_color='k') # Bibliografia: [Ref.9]
plt.axis("off") # Bibliografia: [Ref.10]
plt.title("Xarxa multicapa de la sèrie Joc de trons. Cada capa de la xarxa és una temporada de la sèrie", fontsize=90)
plt.show()